<?php
if(!interface_exists('XDAO', false)){
	require(XFRAMEWORK_DIR.'/module/orm/XDAO.php');
}
class XARDAO implements XDAO{
	private static $daos = array();
	protected $annotation = null;
	protected $driver = null;
	protected $table = null;
	protected $loader = null;
	protected $mappings = array();
	
	private function __construct($modelName){	
		//load model		
		$this->loader = XClassLoader::loader($modelName);
		if(!$this->loader->isSubclassOf('XARRecord')){
			throw new XORMException("class {$modelName} does not extend XARRecord");
		}
		$this->annotation = XAnnotation::singleton($modelName);
		//load driver
		$driverName = $this->annotation()->get('Driver.name');
		$this->driver = XDB::singleton()->driver($driverName);		
		//load table		
		$tableName = $this->annotation()->get('Table.name');
		if(is_null($tableName)){
			$tableName = x_name_to_c($modelName);
		}
		$this->table = $this->driver()->loadTable($tableName);
		$pk = $this->annotation()->get('Table.pk');
		if(!is_null($pk)){
			$this->table->setPK($pk);
		}
		$this->table()->validate();
	}
	
	/**
	 * @return XARDAO
	 */	
	public static function dao($model){
		if (!isset(self::$daos[$model])) {
			self::$daos[$model] = new self($model);
		}
		return self::$daos[$model];
	}
	
	/**
	 * @return XARDAO
	 */	
	public function setDriver(XDriver $driver){
		$this->driver = $driver;
		return $this;
	}
	
	/**
	 * @return XDBDriver
	 */	
	public function driver(){
		return $this->driver;
	}	
	
	/**
	 * @return XTable
	 */	
	public function table(){
		return $this->table;
	}
	
	/**
	 * @return XAnnotation
	 */	
	public function annotation(){
		return $this->annotation;
	}
	
	public function insert(XModel $model, array $params = array()){
		foreach($params as $key => $value){
			$model->set($key, $value);
		}
		$result = $this->driver()->insert($this->table(), $model);
		if(!$result){
			return $result;
		}
		$model->set($this->table()->PK(), $this->driver()->lastId());
		return true;
	}
	
	public function update(XModel $model, array $params = array()){
		foreach($params as $key => $value){
			$model->set($key, $value);
		}
		return $this->driver()->update($this->table(), $model);
	}
	
	public function remove(XModel $model){
		$result = $this->driver()->remove($this->table(), $model);
		if(!$result){
			return $result;
		}
		foreach($this->table()->columns() as $key => $column){
			$model->set($key, null);
		}
		return true;
	}
	
	public function query($sql){
		return $this->driver()->query($sql);
	}
		
	public function queryCriteria(XCriteria $criteria){
		return $this->driver()->queryCriteria($this->table(), $criteria);
	}

	public function exec($sql){
		return $this->driver()->exec($sql);
	}
	
	public function offsetKeyword(){
		return $this->driver()->offsetKeyword();
	}
	
	public function sizeKeyword(){
		return $this->driver()->sizeKeyword();
	}
	
	/**
	 * @return XModel
	 */	
	public function find(XCriteria $criteria, $asArray = false){
		$results = $this->driver()->queryCriteria($this->table(), $criteria);
		if($asArray){
			if(!is_array($results)){
				return array();
			}
			return array_shift($results);
		}
		$class = $this->loader->name();
		foreach($results as $result){
			$model = XFactory::singleton()->get($class);
			foreach($result as $key => $value){
				$model->set($key, $value);
			}
			$model->setSaved(true);
			return $model;
		}
		return null;
	}
	
	/**
	 * @return array
	 */		
	public function findAll(XCriteria $criteria, $asArray = false){
		$results = $this->driver()->queryCriteria($this->table(), $criteria);
		if($asArray){
			return $results;
		}
		$models = array();
		$class = $this->loader->name();
		foreach($results as $result){
			$model = XFactory::singleton()->get($class);
			foreach($result as $key => $value){
				$model->set($key, $value);
			}
			$model->setSaved(true);
			$models[] = $model;
		}
		return $models;
	}
	
	public function escape($str){
		return $this->driver()->escape($str);
	}
	
	public function relation(XModel $model, $prop){
		$mapping = $this->mapping($prop);
		switch(get_class($mapping)){
			case 'XManyToMany':
				return $mapping->result($model->get($this->uniqueId()));
				break;
			default:			
				return $mapping->result($model->get($mapping->column()));
				break;				
		}
		return null;
	}
	
	public function uniqueId(){
		return $this->table()->PK();
	}
	
	public function defaultForeignKey(){
		return x_name_to_c($this->table()->name()).'_id';
	}
	
	/**
	 * @return XMapping
	 */
	public function mapping($prop){
		if(isset($this->mappings[$prop])){
			return $this->mappings[$prop];
		}
		$annotation = $this->annotation()->property($prop);
		foreach(array(
				'ManyToMany',
				'ManyToOne',
				'OneToMany',
				'OneToOne',
			) as $relation){
			if(!isset($annotation[$relation])){
				continue;
			}
			$class = 'X'.$relation;
			if(!class_exists($class, false)){
				continue;
			}
			$target = isset($annotation[$relation]['targetEntity']) ? $annotation[$relation]['targetEntity'] : x_class_name($prop);
			$column = isset($annotation[$relation]['column']) ? $annotation[$relation]['column'] : null;
			$foreignKey = isset($annotation[$relation]['foreignKey']) ? $annotation[$relation]['foreignKey'] : null;
			$mapping = new $class();
			$mapping->setTarget($target);
			switch($relation){
				case 'OneToOne':
				case 'OneToMany':
					$mapping->setColumn(!is_null($column) ? $column : $this->uniqueId());
					$mapping->setForeignKey(!is_null($foreignKey) ? $foreignKey : $this->defaultForeignKey());
					break;					
				case 'ManyToOne':			
					$mapping->setColumn(!is_null($column) ? $column : x_name_to_c($prop).'_id');
					break;
				case 'ManyToMany':
					$relation = isset($annotation[$relation]['relation']) ? $annotation[$relation]['relation'] : get_class($model).$target;
					$mapping->setRelation($relation);
					$mapping->setColumn(!is_null($column) ? $column : $this->defaultForeignKey());
					$mapping->setForeignKey(!is_null($foreignKey) ? $foreignKey : x_name_to_c($prop).'_id');
					break;
			}
			foreach(array('asc', 'desc') as $order){
				if(isset($annotation[$relation][$order])){
					$mapping->setOrders(array(
						$annotation[$relation][$order] => $order,
					));
				}
			}
			$this->mappings[$prop] = $mapping;
			return $this->mappings[$prop];
		}
		throw new XORMException("Relation {$prop} of Class {$this->loader->name()} is not configured properly");
	}
	
}
?>